#include "OgreGLESHardwareIndexBuffer.h"
#include "OgreGLESHardwareBufferManager.h"
#include "OgreException.h"

namespace Ogre
{
	GLESHardwareIndexBuffer::GLESHardwareIndexBuffer(HardwareBufferManagerBase* mgr,
													 IndexType idxType,
													 size_t numIndexes,
													 HardwareBuffer::Usage usage,
													 bool useShadowBuffer)
		: HardwareIndexBuffer(mgr, idxType, numIndexes, usage, false, useShadowBuffer)
	{
		if (idxType == HardwareIndexBuffer::IT_32BIT)
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
					"32 bit hardware buffers are not allowed in OpenGL ES."
					"GLESHardwareIndexBuffer");
		}
		
		if (!useShadowBuffer)
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
						"Only support with shadowBuffer",
						"GLESHardwareIndexBuffer");
		}
		
		glGenBuffers(1, &mBufferId);
		GL_CHECK_ERROR;
		
		if (!mBufferId)
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
						"Cannot create GL index buffer",
						"GLESHardwareIndexBuffer::GLESHardwareIndexBuffer");
		}
		
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBufferId);
		GL_CHECK_ERROR;
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, mSizeInBytes, NULL,
					 GLESHardwareBufferManager::getGLUsage(usage));
		GL_CHECK_ERROR;
	}
	
	GLESHardwareIndexBuffer::~GLESHardwareIndexBuffer()
	{
		glDeleteBuffers(1, &mBufferId);
		GL_CHECK_ERROR;
	}
	
	void Ogre::GLESHardwareIndexBuffer::unlockImpl(void)
	{
		if (mLockedToScratch)
		{
			if (mScratchUploadOnUnlock)
			{
				writeData(mScratchOffset, mScratchSize, mScratchPtr,
						  mScratchOffset == 0 && mScratchSize == getSizeInBytes());
			}
			
			static_cast<GLESHardwareBufferManager*>(
					HardwareBufferManager::getSingletonPtr())->deallocateScratch(mScratchPtr);
					
			mLockedToScratch = false;
		}
		else
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
						"Lock to scratch is only supported",
						"GLESHardwareIndexBuffer::unlockImpl");
		}
	}
	
	void* GLESHardwareIndexBuffer::lockImpl(size_t offset,
											size_t length,
											LockOptions options)
	{
		if (mIsLocked)
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
					    "Invalid attempt to lock an index buffer that has already been locked",
						"GLESHardwareIndexBuffer::lock");
		}
		
		void* retPtr = 0;
		
		if (length < OGRE_GL_MAP_BUFFER_THRESHOLD)
		{
			
			retPtr = static_cast<GLESHardwareBufferManager*>
					(HardwareBufferManager::getSingletonPtr())->allocateScratch((uint32)length);
			if (retPtr)
			{
				mLockedToScratch = true;
				mScratchOffset = offset;
				mScratchSize = length;
				mScratchPtr = retPtr;
				mScratchUploadOnUnlock = (options != HBL_READ_ONLY);
				
				if (options != HBL_DISCARD)
				{
					readData(offset, length, retPtr);
				}
			}
		}
		else
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
						"Invalid Buffer lockSize",
						"GLESHardwareIndexBuffer::lock");
		}
		
		return retPtr;
	}
	
	void GLESHardwareIndexBuffer::readData(size_t offset,
										   size_t length,
										   void* pDest)
	{
		if (mUseShadowBuffer)
		{
			void* srcData = mpShadowBuffer->lock(offset, length, HBL_READ_ONLY);
			memcpy(pDest, srcData, length);
			mpShadowBuffer->unlock();
		}
		else
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
						"Reading hardware buffer is not supported",
						"GLESHardwareIndexBuffer::readData");
		}
	}
	
	void GLESHardwareIndexBuffer::writeData(size_t offset, size_t length,
											const void* pSource,
											bool discardWholeBuffer)
	{
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBufferId);
		GL_CHECK_ERROR;
		
		if (mUseShadowBuffer)
		{
			void* destData = mpShadowBuffer->lock(offset, length,
												  discardWholeBuffer ? HBL_DISCARD : HBL_NORMAL);
			memcpy(destData, pSource, length);
			mpShadowBuffer->unlock();
		}
		
		if (offset == 0 && length == mSizeInBytes)
		{
			glBufferData(GL_ELEMENT_ARRAY_BUFFER, mSizeInBytes, pSource,
						 GLESHardwareBufferManager::getGLUsage(mUsage));
			GL_CHECK_ERROR;
		}
		else
		{
			if (discardWholeBuffer)
			{
				glBufferData(GL_ELEMENT_ARRAY_BUFFER, mSizeInBytes, NULL,
								GLESHardwareBufferManager::getGLUsage(mUsage));
			}
			
			glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, length, pSource);
			GL_CHECK_ERROR;
		}
	}
	
	void GLESHardwareIndexBuffer::_updateFromShadow(void)
	{
		if (mUseShadowBuffer && mShadowUpdated && !mSuppressHardwareUpdate)
		{
			const void* srcData = mpShadowBuffer->lock(mLockStart, mLockSize,
													   HBL_READ_ONLY);
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBufferId);
			GL_CHECK_ERROR;
			
			if (mLockStart == 0 && mLockSize == mSizeInBytes)
			{
				glBufferData(GL_ELEMENT_ARRAY_BUFFER, mSizeInBytes, srcData,
							 GLESHardwareBufferManager::getGLUsage(mUsage));
				GL_CHECK_ERROR;
			}
			else
			{
				glBufferSubData(GL_ELEMENT_ARRAY_BUFFER,
								mLockStart, mLockSize, srcData);
				GL_CHECK_ERROR;
			}
			
			mpShadowBuffer->unlock();
			mShadowUpdated = false;
		}
	}
}
